﻿
namespace WekaSharp

open WekaSharp
open weka


module Dataset =
    open weka.core
    open weka.core.converters
    open weka.filters

    let checkDatasetLabel (ds:Dataset) = 
        if ds.classIndex() = -1 then failwith "Class/label index is not set for this dataset."

    let removeClassAttribute (ds:Dataset) = 
        checkDatasetLabel ds
        let rFilter = new filters.unsupervised.attribute.Remove()
        rFilter.setAttributeIndices("" + (ds.classIndex() + 1).ToString())
        rFilter.setInputFormat(ds) |> ignore
        Filter.useFilter(ds, rFilter)

    let setClassIndexWithLastAttribute (ds:Dataset) = 
        ds.setClassIndex(ds.numAttributes() - 1)
        ds
        
    let readDataFile (ftype: DatafileType) (fname:string) =
        let loader =
            match ftype with
                | Arff -> new converters.ArffLoader() :> AbstractFileLoader
                | LibSVM -> new converters.LibSVMLoader() :> AbstractFileLoader
                | Csv -> new converters.CSVLoader() :> AbstractFileLoader
                | Svmlight -> new converters.SVMLightLoader() :> AbstractFileLoader
        try
            loader.setFile(new java.io.File(fname))
            loader.getDataSet()
        with
            | _ -> failwith "dataset loading error"

    let saveDataFile (ftype: DatafileType) (ds:Dataset) (fname:string) = 
        let saver = 
            match ftype with
                | Arff -> new converters.ArffSaver() :> AbstractFileSaver
                | LibSVM -> new converters.LibSVMSaver() :> AbstractFileSaver
                | Csv -> new converters.CSVSaver() :> AbstractFileSaver
                | Svmlight -> new converters.SVMLightSaver() :> AbstractFileSaver
        try
            saver.setInstances(ds)
            saver.setFile(new java.io.File(fname))
            saver.writeBatch()
        with
            | _ -> failwith "dataset saving error"

    let readArff  = readDataFile DatafileType.Arff  
    let readArffLastAttributeAsLabel fname = 
        fname |> readDataFile DatafileType.Arff |> setClassIndexWithLastAttribute
    let readLibsvm = readDataFile DatafileType.LibSVM  
    let readCsv = readDataFile DatafileType.Csv 
    let readSvmlight = readDataFile DatafileType.Svmlight  
            
    let saveArff (ds:Dataset) (fname:string) = saveDataFile DatafileType.Arff  ds fname
    let saveLibsvm (ds:Dataset) (fname:string) = saveDataFile DatafileType.LibSVM  ds fname
    let saveCsv (ds:Dataset) (fname:string) = saveDataFile DatafileType.Csv  ds fname
    let saveSvmlight (ds:Dataset) (fname:string) = saveDataFile DatafileType.Svmlight ds fname (* svmlight saver is bug! *)


    /// randomizie the order of instances
    let randomOrder (ds:Dataset) = 
        let rFilter = new filters.unsupervised.instance.Randomize()
        rFilter.setRandomSeed(System.DateTime.Now.Millisecond)
        rFilter.setInputFormat(ds) |> ignore
        filters.Filter.useFilter(ds, rFilter)

    /// randomly split a data set into two parts
    /// with a ratio between the two parts
    let randomSplit (ratio:float) (ds:Dataset) =
        let newds = randomOrder ds
        let nTrain = int( (float (ds.numInstances())) * ratio )
        let nTest = ds.numInstances() - nTrain
        new core.Instances(newds, 0, nTrain-1), new core.Instances(newds, nTrain, nTest)

    /// Standardizes all numeric attributes in the given dataset to 
    /// have zero mean and unit variance (apart from the class attribute, if set).
    let standardize (ds:Dataset) = 
        let sFilter = new filters.unsupervised.attribute.Standardize()
        sFilter.setInputFormat(ds) |> ignore
        filters.Filter.useFilter(ds, sFilter)


    /// Centers all numeric attributes in the given dataset to have zero 
    /// mean (apart from the class attribute, if set).
    let center (ds:Dataset) =
        let cFilter = new filters.unsupervised.attribute.Center()
        cFilter.setInputFormat(ds) |> ignore
        filters.Filter.useFilter(ds, cFilter)

    /// discretizes a range of numeric attributes in the 
    /// dataset into nominal attributes.
    let unsupervisedDiscrete (bins:int) (isBinary:bool) (ds:Dataset) =
        let dFilter = new filters.unsupervised.attribute.Discretize()
        dFilter.setInputFormat(ds) |> ignore
        dFilter.setBins(bins)
        dFilter.setMakeBinary(isBinary)
        filters.Filter.useFilter(ds, dFilter)

    

    (* functions to transfrom data from F# to weka Instances *)

    /// from2DArray
    /// intAsNominal: if a column contains all integers, then treat this column as nominal column
    /// if use intAsNominal, make sure the all nominal columns starts at 0!!
    let from2DArray (d:float[,]) (intAsNominal:bool) = 
        let seq2FastVector (s: 'a seq) = 
            let f = new core.FastVector()
            for i in s do
                f.addElement(i)
            f

        let nrow = d.GetLength(0)
        let ncol = d.GetLength(1)
        let isInt (x:float) = abs (x - (float (int x))) < 1e-12
        let attributes = 
            Array.init ncol (fun i -> 
                if intAsNominal then
                    let allInt = {0..nrow-1} |> Seq.forall (fun j -> isInt d.[j,i])
                    if allInt then
                        let vals = new core.FastVector()
                        let nDifferentValues = {0..nrow-1} |> Seq.map (fun j -> d.[j,i]) |> Seq.distinct |> Seq.length
                        for k=1 to nDifferentValues do 
                            vals.addElement("Attr_" + i.ToString() + "Val_" + k.ToString())
                        new core.Attribute("Attr_" + i.ToString(), vals)
                    else
                        new core.Attribute("Attr_" + i.ToString())
                else
                    new core.Attribute("Attr_" + i.ToString())
                    )
            |> seq2FastVector

        let instances = 
            Array.init nrow (fun i -> 
                new core.Instance(1.0, {0..ncol-1} |> Seq.map (fun j -> d.[i,j]) |> Seq.toArray))

        let wekaDataset = new core.Instances("from_F#_2DArray", attributes, 0)
        for inst in instances do
            wekaDataset.add(inst)
            inst.setDataset(wekaDataset)
        
        wekaDataset
        
    /// add classlabels to a dataset
    let addClassLabels (labels: string seq) (ds: Dataset) = 
        let nval = Seq.length labels
        if nval <> ds.numInstances() then
            failwith "addClassLabels: label size does not match"

        let distinct = Seq.distinct labels 
        let nominals = String.concat "," distinct
        let addFilter = new filters.unsupervised.attribute.Add()
        addFilter.setOptions(core.Utils.splitOptions ("-T NOM -N class-label -L " + nominals + " -C last"))
        addFilter.setInputFormat(ds) |> ignore
        let newds = Filter.useFilter(ds, addFilter)
        newds.setClassIndex(newds.numAttributes() - 1)
        labels
        |> Seq.iteri (fun i str -> newds.instance(i).setValue(newds.classIndex(), str))

        newds

    